package io.github.guojiaxing1995.easyJmeter.common.jmeter;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.guojiaxing1995.easyJmeter.common.configuration.InfluxDBProperties;
import io.github.guojiaxing1995.easyJmeter.common.util.ThreadUtil;
import io.github.guojiaxing1995.easyJmeter.common.util.ZipUtil;
import io.github.guojiaxing1995.easyJmeter.dto.task.TaskProgressMachineDTO;
import io.github.guojiaxing1995.easyJmeter.model.CaseDO;
import io.github.guojiaxing1995.easyJmeter.model.JFileDO;
import io.github.guojiaxing1995.easyJmeter.model.ReportDO;
import io.github.guojiaxing1995.easyJmeter.model.TaskDO;
import io.github.guojiaxing1995.easyJmeter.repository.ReportRepository;
import io.github.guojiaxing1995.easyJmeter.service.JFileService;
import io.github.guojiaxing1995.easyJmeter.vo.CutFileVO;
import io.github.guojiaxing1995.easyJmeter.vo.MachineCutFileVO;
import io.github.talelin.autoconfigure.exception.ParameterException;
import io.socket.client.Socket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.config.CSVDataSet;
import org.apache.jmeter.report.dashboard.ReportGenerator;
import org.apache.jmeter.reporters.ResultCollector;
import org.apache.jmeter.reporters.Summariser;
import org.apache.jmeter.save.SaveService;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.property.StringProperty;
import org.apache.jmeter.threads.SetupThreadGroup;
import org.apache.jmeter.threads.ThreadGroup;
import org.apache.jmeter.timers.ConstantThroughputTimer;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.backend.BackendListener;
import org.apache.jorphan.collections.HashTree;
import org.apache.jorphan.collections.SearchByClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JmeterExternal {
    private static final Logger log = LoggerFactory.getLogger(JmeterExternal.class);
    private String version;
    private String path;
    private String address;
    private Boolean isOnline;
    private final Socket socket;
    public static final long JTL_SIZE_THRESHOLD = 104857600L;
    public static boolean shouldUseDistributedProcessing(String jtlFilePath) {
        if (jtlFilePath == null) {
            return true;
        } else {
            File jtlFile = new File(jtlFilePath);
            if (jtlFile.exists() && jtlFile.canRead()) {
                long fileSize = jtlFile.length();
                if (fileSize > 104857600L) {
                    log.info("JTL文件大小为{}MB，超过阈值{}MB，使用分布式处理", fileSize / 1048576L, 100L);
                    return true;
                } else {
                    log.info("JTL文件大小为{}MB，低于阈值{}MB，使用原始处理", fileSize / 1048576L, 100L);
                    return false;
                }
            } else {
                return true;
            }
        }
    }


    public JmeterExternal(Socket socket) {
        this.socket = socket;
        this.path = System.getenv("JMETER_HOME");
        this.version();
        this.ipAddress();
        this.online();
    }

    public JmeterExternal() {
        this.socket = null;
        this.path = System.getProperty("user.dir") + "/apache-jmeter";
        this.serverVersion();
        this.ipAddress();
        this.online();
    }

    public void serverVersion() {
        JMeterUtils.setJMeterHome(this.path);
        JMeterUtils.loadJMeterProperties(Paths.get(this.path, "/bin/jmeter.properties").toString());
        this.version = JMeterUtils.getJMeterVersion();
    }

    public void version() {
        Path jmeterPath = Paths.get(this.path, "/bin/jmeter");
        ProcessBuilder processBuilder = new ProcessBuilder(new String[]{jmeterPath.toString(), "--version"});
        processBuilder.environment().putAll(System.getenv());
        StringBuilder outputString = new StringBuilder();
        Process process = null;

        try {
            process = processBuilder.start();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

            try {
                String line;
                while ((line = reader.readLine()) != null) {
                    outputString.append(line).append("\n");
                }

                int suffix = process.waitFor();
            } catch (Throwable var18) {
                try {
                    reader.close();
                } catch (Throwable var16) {
                    var18.addSuppressed(var16);
                }

                throw var18;
            }

            reader.close();
        } catch (IOException | InterruptedException e) {
            log.error("获取jmeter版本失败", e);
        } finally {
            process.destroy();
        }

        String version_str = outputString.toString().replaceAll("[_|\\\\/\\s\\r\\n]", "");
        String prefix = Pattern.quote("<");
        String suffix = "Copyright";
        String regex = prefix + "(.*?)" + suffix;
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(version_str);
        if (matcher.find()) {
            this.version = matcher.group(1).trim();
        }

        this.version = this.version;
    }

    public void ipAddress() {
        try {
            Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
            InetAddress addr = null;

            while (allNetInterfaces.hasMoreElements()) {
                NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
                Enumeration<InetAddress> addresses = netInterface.getInetAddresses();

                while (addresses.hasMoreElements()) {
                    InetAddress ipTmp = (InetAddress) addresses.nextElement();
                    if (ipTmp != null && ipTmp instanceof Inet4Address && ipTmp.isSiteLocalAddress() && !ipTmp.isLoopbackAddress() && ipTmp.getHostAddress().indexOf(":") == -1) {
                        addr = ipTmp;
                    }
                }
            }

            if (addr != null) {
                this.address = addr.getHostAddress();
            } else {
                log.error("获取本机ip异常");
            }
        } catch (SocketException e) {
            log.error("获取本机ip异常", e);
        }

    }

    public void online() {
        this.isOnline = !this.version.isEmpty();
    }

    public Boolean isPropertiesExist(String property) {
        File file = new File(Paths.get(this.path, "/bin/user.properties").toString());

        try {
            try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
                String line;
                do {
                    if ((line = reader.readLine()) == null) {
                        return false;
                    }
                } while (!line.contains(property));

                return true;
            }
        } catch (IOException e) {
            log.error("读取user.properties文件失败", e);
            throw new RuntimeException(e);
        }
    }

    public void setProperties(String property) {
        if (!this.isPropertiesExist(property)) {
            File file = new File(Paths.get(this.path, "/bin/user.properties").toString());

            try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(file, true)))) {
                writer.println(property);
            } catch (IOException e) {
                log.error("写入user.properties失败，属性：" + property, e);
                throw new RuntimeException(e);
            }
        }

    }

    public void addProperties() {
        // 依赖jar
        this.setProperties("plugin_dependency_paths=../tmp/dependencies/;");
        // BeanShell Server properties
        this.setProperties("beanshell.server.port=38927");
        this.setProperties("beanshell.server.file=../extras/startup.bsh");
    }

    public void initJMeterUtils() {
        JMeterUtils.setJMeterHome(this.path);
        JMeterUtils.loadJMeterProperties(Paths.get(this.path, "/bin/jmeter.properties").toString());
    }

    public void initServerJmeterUtils(TaskDO taskDO) {
        JMeterUtils.setJMeterHome(this.path);
        JMeterUtils.loadJMeterProperties(Paths.get(this.path, "/bin/jmeter.properties").toString());
        if (taskDO != null) {
            Integer granularity = taskDO.getGranularity();
            if (granularity == 0) {
                Integer duration = taskDO.getDuration();
                if (duration / 60 <= 15) {
                    granularity = 3;
                } else if (duration / 60 > 15 && duration / 60 <= 30) {
                    granularity = 6;
                } else if (duration / 60 > 30 && duration / 60 <= 60) {
                    granularity = 10;
                } else {
                    granularity = 30;
                }
            }

            JMeterUtils.setProperty("jmeter.reportgenerator.overall_granularity", String.valueOf(granularity * 1000));
        }

        JMeterUtils.setProperty("jmeter.save.saveservice.output_format", "xml");
        JMeterUtils.setProperty("jmeter.save.saveservice.response_data", "true");
        JMeterUtils.setProperty("jmeter.save.saveservice.response_data.on_error", "true");
        JMeterUtils.setProperty("jmeter.save.saveservice.samplerData", "true");
        JMeterUtils.setProperty("jmeter.save.saveservice.responseHeaders", "true");
        JMeterUtils.setProperty("jmeter.save.saveservice.requestHeaders", "true");
        JMeterUtils.setLocale(Locale.ENGLISH);
    }

    public void editJmxConfig(TaskDO taskDO, InfluxDBProperties influxDBProperties) throws IOException {
        File directory = new File(Paths.get(this.path, "/tmp/").toString());
        File[] files = directory.listFiles();
        File jmxfile = null;
        List<File> csvFiles = new ArrayList();
        if (files != null) {
            for (File file : files) {
                if (file.isFile() && file.getName().toLowerCase().endsWith(".csv")) {
                    csvFiles.add(file);
                }

                if (file.isFile() && file.getName().toLowerCase().endsWith(".jmx")) {
                    jmxfile = file;
                }
            }
        }

        if (jmxfile != null) {
            HashTree testPlanTree = SaveService.loadTree(jmxfile);
            SearchByClass<CSVDataSet> csvDataSetSearch = new SearchByClass(CSVDataSet.class);
            testPlanTree.traverse(csvDataSetSearch);

            for (File csvFile : csvFiles) {
                for (CSVDataSet csvDataSet : csvDataSetSearch.getSearchResults()) {
                    String csvOldPathName = (new File(csvDataSet.getProperty("filename").toString())).getName();
                    if (csvOldPathName.equals(csvFile.getName())) {
                        csvDataSet.setProperty("filename", csvFile.getAbsolutePath());
                    }
                }
            }

            SearchByClass<ThreadGroup> threadGroupSearch = new SearchByClass(ThreadGroup.class);
            testPlanTree.traverse(threadGroupSearch);

            for (ThreadGroup threadGroup : threadGroupSearch.getSearchResults()) {
                threadGroup.setNumThreads(taskDO.getNumThreads() / taskDO.getMachineNum());
                threadGroup.setDuration((long) taskDO.getDuration());
                threadGroup.setScheduler(true);
                threadGroup.setRampUp(taskDO.getRampTime());
                threadGroup.getSamplerController().setProperty("LoopController.loops", -1);
            }
            // 添加常数吞吐量定时器
            int qpsLimit = taskDO.getQpsLimit() == 0 ? 999999999 : Math.round((float) taskDO.getQpsLimit() / (float) taskDO.getMachineNum());
            ConstantThroughputTimer constantThroughputTimer = new ConstantThroughputTimer();
            constantThroughputTimer.setName("Constant Throughput Timer");
            constantThroughputTimer.setEnabled(true);
            constantThroughputTimer.setCalcMode(1);
            constantThroughputTimer.setProperty(new StringProperty("TestElement.gui_class", "TestBeanGUI"));
            constantThroughputTimer.setProperty(new StringProperty("TestElement.test_class", "ConstantThroughputTimer"));
            String throughput = "${__jexl3(${__P(throughput, " + qpsLimit + ")}*60,)}";
            constantThroughputTimer.setProperty("throughput", throughput);
            testPlanTree.add(testPlanTree.getArray()[0], constantThroughputTimer);

            //添加influxdb后置监听器
            if (taskDO.getRealtime()) {
                String database = influxDBProperties.getDatabase();
                String url = influxDBProperties.getUrl();
                String taskId = taskDO.getTaskId();
                Arguments arguments = new Arguments();
                arguments.setProperty(new StringProperty("TestElement.gui_class", "ArgumentsPanel"));
                arguments.setProperty(new StringProperty("TestElement.test_class", "Arguments"));
                arguments.setProperty(new StringProperty("TestElement.name", "arguments"));
                arguments.setEnabled(true);
                arguments.addArgument("influxdbMetricsSender", "org.apache.jmeter.visualizers.backend.influxdb.HttpMetricsSender", "=");
                arguments.addArgument("influxdbUrl", url + "/write?db=" + database, "=");
                arguments.addArgument("application", taskId, "=");
                arguments.addArgument("measurement", "jmeter", "=");
                arguments.addArgument("summaryOnly", "false", "=");
                arguments.addArgument("samplersRegex", ".*", "=");
                arguments.addArgument("percentiles", "90;95;99", "=");
                arguments.addArgument("testTitle", taskId, "=");
                arguments.addArgument("eventTags", this.address, "=");
                BackendListener backendListener = new BackendListener();
                backendListener.setProperty(new StringProperty("TestElement.gui_class", "BackendListenerGui"));
                backendListener.setProperty(new StringProperty("TestElement.test_class", "BackendListener"));
                backendListener.setProperty(new StringProperty("TestElement.name", "influxdbBackendListener"));
                backendListener.setEnabled(true);
                backendListener.setClassname("org.apache.jmeter.visualizers.backend.influxdb.InfluxdbBackendListenerClient");
                backendListener.setArguments(arguments);
                testPlanTree.add(testPlanTree.getArray()[0], backendListener);
            }

            String var10004 = this.path;

            try (FileOutputStream outputStream = new FileOutputStream(new File(var10004 + "/tmp/" + taskDO.getTaskId() + ".jmx"))) {
                SaveService.saveTree(testPlanTree, outputStream);
            }
        }

    }


    public Map<String, String> editJmxDebugConfig(CaseDO caseDO, Long debugId, JFileService jFileService) throws IOException {
        // jmx文件路径
        String jmxPath = null;
        // 文件下载目录
        String dir = Paths.get(jFileService.getStoreDir(), caseDO.getId().toString(), debugId.toString()).toString();
        // jtl 文件路径
        String jtlPath = Paths.get(dir, "debug.jtl").toString();
        // 下载jmx文件
        String jmxStr = caseDO.getJmx();
        List<JFileDO> jmxFileDOList= Arrays.stream(jmxStr.isEmpty() ? new String[]{} : jmxStr.split(","))
                .map(Integer::parseInt).map(jFileService::searchById).collect(Collectors.toList());
        if (jmxFileDOList.isEmpty()) {
            throw new ParameterException();
        } else {
            jmxPath = jFileService.downloadFile(jmxFileDOList.get(0).getId(), dir);
        }
        // jar文件下载
        String jarStr = caseDO.getJar();
        List<JFileDO> jarFileDOList= Arrays.stream(jarStr.isEmpty() ? new String[]{} : jarStr.split(","))
                .map(Integer::parseInt).map(jFileService::searchById).collect(Collectors.toList());
        for (JFileDO jFileDO: jarFileDOList) {
            jFileService.downloadFile(jFileDO.getId(), dir);
        }
        // csv 文件下载
        String csvStr = caseDO.getCsv();
        List<JFileDO> csvFileDOList= Arrays.stream(csvStr.isEmpty() ? new String[]{} : csvStr.split(","))
                .map(Integer::parseInt).map(jFileService::searchById).collect(Collectors.toList());
        List<File> csvFiles = new ArrayList<>();
        for (JFileDO jFileDO: csvFileDOList) {
            File csvFile = new File(jFileService.downloadFile(jFileDO.getId(), dir));
            csvFiles.add(csvFile);
        }
        // 修改压测配置
        HashTree testPlanTree = SaveService.loadTree(new File(jmxPath));
        SearchByClass<SetupThreadGroup> setupThreadGroupSearch = new SearchByClass<>(SetupThreadGroup.class);
        testPlanTree.traverse(setupThreadGroupSearch);
        for (SetupThreadGroup setupThreadGroup : setupThreadGroupSearch.getSearchResults()) {
            setupThreadGroup.setNumThreads(1);
            setupThreadGroup.setScheduler(false);
            setupThreadGroup.setRampUp(1);
            setupThreadGroup.getSamplerController().setProperty("LoopController.loops", 1);
        }
        // 修改jmx中csv配置
        SearchByClass<CSVDataSet> csvDataSetSearch = new SearchByClass<>(CSVDataSet.class);
        testPlanTree.traverse(csvDataSetSearch);
        for (File csvFile : csvFiles) {
            for (CSVDataSet csvDataSet : csvDataSetSearch.getSearchResults()) {
                String csvOldPathName = new File(csvDataSet.getProperty("filename").toString()).getName();
                if (csvOldPathName.equals(csvFile.getName())) {
                    csvDataSet.setProperty("filename", csvFile.getAbsolutePath());
                }
            }
        }
        // 加入查看结果数
        Summariser summer = new Summariser("debug");
        ResultCollector resultCollector = new ResultCollector(summer);
        resultCollector.setEnabled(true);
        resultCollector.setProperty(new StringProperty(TestElement.GUI_CLASS, "ViewResultsFullVisualizer"));
        resultCollector.setProperty(new StringProperty(TestElement.TEST_CLASS, "ResultCollector"));
        resultCollector.setProperty(new StringProperty(TestElement.NAME, "debug"));
        resultCollector.setFilename(jtlPath);
        testPlanTree.add(testPlanTree.getArray()[0], resultCollector);
        // 写回jmx文件
        try (FileOutputStream outputStream = new FileOutputStream(new File(jmxPath))) {
            SaveService.saveTree(testPlanTree, outputStream);
        }

        Map<String, String> map = new HashMap<>();
        map.put("jmxPath", jmxPath);
        map.put("jtlPath", jtlPath);

        return map;
    }

    public void runJmeter(TaskDO taskDO) {
        String jmeterPath = Paths.get(this.path, "/bin/jmeter").toString();
        String jmxPath = Paths.get(this.path, "/tmp/" + taskDO.getTaskId() + ".jmx").toString();
        String logPath = Paths.get(this.path, "/tmp/jmeter.log").toString();
        String jtlPath = Paths.get(this.path, "/tmp/result.jtl").toString();
        String reportPath = Paths.get(this.path, "/tmp/report").toString();
        ProcessBuilder processBuilder =
                new ProcessBuilder(new String[]{jmeterPath, "-n", "-t", jmxPath, "-j", logPath, "-L", "all=" + taskDO.getLogLevel().getDesc(), "-l", jtlPath});
        processBuilder.environment().putAll(System.getenv());
        StringBuilder outputString = new StringBuilder();

        try {
            Process process = processBuilder.start();

            String line;
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                for(; (line = reader.readLine()) != null; log.info(line)) {
                    outputString.append(line).append("\n");
                    if (Thread.currentThread().isInterrupted()) {
                        log.info("压测中的jmeter线程被中断");
                        process.destroy();
                    }

                    if (line.contains("... end of run")) {
                        try {
                            Files.createDirectories(Paths.get(reportPath));
                        } catch (IOException e) {
                            log.error("创建report目录失败", e);
                        }
                    }

                    File report = new File(reportPath);
                    if (report.exists() && ThreadUtil.isProcessContainingName(taskDO.getTaskId() + ".jmx")) {
                        ThreadUtil.killProcessContainingName(taskDO.getTaskId() + ".jmx");
                    }

                    Integer machineProcess = this.getMachineProcess(line, taskDO);
                    if (machineProcess != 0 && !Thread.currentThread().isInterrupted()) {
                        TaskProgressMachineDTO taskProgressMachineDTO = new TaskProgressMachineDTO(taskDO.getTaskId(), this.getAddress(), machineProcess);
                        String taskProgressMachine = (new ObjectMapper()).writeValueAsString(taskProgressMachineDTO);
                        this.socket.emit("machineTaskProgress", new Object[]{taskProgressMachine});
                    }
                }
            }
            int exitCode = process.waitFor();
        } catch (InterruptedException | IOException e) {
            log.error("执行jmeter失败", e);
        }

        log.info(outputString.toString());
    }

    public Integer getMachineProcess(String line, TaskDO taskDO) {
        String pattern = "\\d{2}:(\\d{2}):(\\d{2})";
        Pattern r = Pattern.compile(pattern);
        if (!line.contains("summary =")) {
            return 0;
        } else {
            Matcher m = r.matcher(line);
            if (m.find()) {
                String time = m.group(0);
                String[] parts = time.split(":");
                int hour = Integer.parseInt(parts[0]);
                int minute = Integer.parseInt(parts[1]);
                int second = Integer.parseInt(parts[2]);
                int totalSeconds = hour * 3600 + minute * 60 + second;
                return Math.round((float) totalSeconds / (float) taskDO.getDuration() * 100);
            } else {
                return 0;
            }
        }
    }

    public String createBshFile() {
        String setPropStr = "import org.apache.jmeter.util.JMeterUtils;\n" +
                "setProp(p,v){\n" +
                "    JMeterUtils.getJMeterProperties().setProperty(p, v);\n" +
                "}\n" +
                "setProp(args[0], args[1]);";
        String filePath = Paths.get(this.path, "/tmp/setProp.bsh").toString();
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath))) {
            writer.write(setPropStr);
        } catch (IOException e) {
            log.error("createBshFile", e);
            throw new RuntimeException(e);
        }
        return filePath;
    }

    public void modifyQPSLimit(TaskDO taskDO) {
        String clientJarPath = Paths.get(this.path, "/lib/bshclient.jar").toString();
        Integer qpsLimit;
        if (taskDO.getQpsLimit() == 0) {
            qpsLimit = 999999999;
        } else {
            qpsLimit = Math.round((float) taskDO.getQpsLimit() /taskDO.getMachineNum());
        }
        String bshPath = this.createBshFile();
        ProcessBuilder processBuilder =
                new ProcessBuilder("java", "-jar", clientJarPath, "localhost", "38927", bshPath, "throughput", String.valueOf(qpsLimit));
        processBuilder.environment().putAll(System.getenv());
        try {
            Process process = processBuilder.start();
            try(BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    log.info(line);
                }
            }
            int exitCode = process.waitFor();
        } catch (IOException | InterruptedException e) {
            log.error("调节吞吐量失败", e);
        }
    }

    public void clean(TaskDO taskDO) throws IOException {
        // 如果测试进程仍然存在，则杀掉进程
        if (ThreadUtil.isProcessContainingName(taskDO.getTaskId()+".jmx")) {
            ThreadUtil.killProcessContainingName(taskDO.getTaskId()+".jmx");
        }
        // 将tmp目录重命名为任务目录
        Path tmpPath = Paths.get(this.path, "tmp");
        Path taskPath = Paths.get(this.path, taskDO.getTaskId());
        Files.move(tmpPath, taskPath, StandardCopyOption.REPLACE_EXISTING);
    }

    public void collect(TaskDO taskDO, JFileService jFileService) {
        log.info("开始收集任务 {} 的数据文件", taskDO.getTaskId());
        File jmeterLogPath = new File(Paths.get(this.path, "tmp", "jmeter.log").toString());
        if (!jmeterLogPath.exists()) {
            log.warn("jmeter.log文件不存在: {}", jmeterLogPath.getAbsolutePath());
        } else {
            File newLogPath = new File(Paths.get(this.path, "tmp", this.address + "_jmeter.log").toString());
            boolean LogRename = jmeterLogPath.renameTo(newLogPath);
            if (LogRename) {
                try {
                    JFileDO file = jFileService.createFile(newLogPath.getAbsolutePath());
                    file.setTaskId(taskDO.getTaskId());
                    jFileService.updateById(file);
                    log.info("成功上传jmeter日志: {}", newLogPath.getAbsolutePath());
                } catch (Exception e) {
                    log.error("上传jmeter日志文件失败: {}", e.getMessage(), e);
                }
            } else {
                log.error("重命名jmeter日志文件失败: {} -> {}", jmeterLogPath.getAbsolutePath(), newLogPath.getAbsolutePath());
            }
        }

        File jtlPath = new File(Paths.get(this.path, "tmp", "result.jtl").toString());
        if (!jtlPath.exists()) {
            log.warn("result.jtl文件不存在: {}", jtlPath.getAbsolutePath());
        } else {
            log.info("找到JTL文件: {}, 大小: {}KB", jtlPath.getAbsolutePath(), jtlPath.length() / 1024L);
            String var10002 = this.path;
            String[] var10003 = new String[]{"tmp", null};
            String var10006 = this.address;
            var10003[1] = var10006 + "_" + taskDO.getTaskId() + "_result.jtl";
            File newJtlPath = new File(Paths.get(var10002, var10003).toString());
            boolean jtlRename = jtlPath.renameTo(newJtlPath);
            if (jtlRename) {
                try {
                    JFileDO jtlFile = jFileService.createFile(newJtlPath.getAbsolutePath());
                    jtlFile.setTaskId(taskDO.getTaskId());
                    jFileService.updateById(jtlFile);
                    log.info("成功上传JTL文件: {}, ID: {}", newJtlPath.getAbsolutePath(), jtlFile.getId());
                    if (shouldUseDistributedProcessing(newJtlPath.getAbsolutePath())) {
                        log.info("开始在Agent端本地处理JTL文件...");
                        long localProcessStart = System.currentTimeMillis();
                        FastJtlProcessor processor = new FastJtlProcessor();
                        String statsFilePath = processor.generateStatsFile(taskDO, newJtlPath.getAbsolutePath(), this.address);
                        if (statsFilePath != null) {
                            JFileDO statsFile = jFileService.createFile(statsFilePath);
                            statsFile.setTaskId(taskDO.getTaskId());
                            jFileService.updateById(statsFile);
                            log.info("成功上传统计数据文件: {}, ID: {}, 耗时: {}秒", new Object[]{statsFilePath, statsFile.getId(), (double) (System.currentTimeMillis() - localProcessStart) / (double) 1000.0F});
                        } else {
                            log.warn("JTL本地处理失败，将继续使用原始JTL文件");
                        }
                    } else {
                        log.info("JTL文件大小小于{}MB阈值，跳过Agent端本地处理", 100L);
                    }
                } catch (Exception e) {
                    log.error("上传JTL文件失败: {}", e.getMessage(), e);
                }
            } else {
                log.error("重命名JTL文件失败: {} -> {}", jtlPath.getAbsolutePath(), newJtlPath.getAbsolutePath());
            }
        }
    }

    public void downloadConfigFile(TaskDO taskDO, JFileService jFileService, MachineCutFileVO machineCutFileVO) {
        String tmpDir = Paths.get(this.path, "tmp").toString();
        String dependencyDir = Paths.get(this.path,"tmp", "dependencies").toString();
        // 下载分配给本机的切分文件
        if (machineCutFileVO.getMachineDOCutFileVOListMap() != null) {
            Map<String, List<CutFileVO>> map = machineCutFileVO.getMachineDOCutFileVOListMap();
            for (Map.Entry<String, List<CutFileVO>> entry : map.entrySet()) {
                if (entry.getKey().equals(new JmeterExternal(socket).getAddress())) {
                    jFileService.downloadCutFile(entry.getValue(), tmpDir);
                }
            }
        }
        // 下载无需切分的文件
        String[] csvFileIds = (taskDO.getCsv() != null && !taskDO.getCsv().isEmpty()) ? taskDO.getCsv().split(",") : new String[]{};
        for (String csvFileId : csvFileIds){
            JFileDO jFileCsvDO = jFileService.searchById(Integer.valueOf(csvFileId));
            if (!jFileCsvDO.getCut()){
                jFileService.downloadFile(Integer.valueOf(csvFileId), tmpDir);
            }
        }
        String[] jmxFileIds = (taskDO.getJmx() != null && !taskDO.getJmx().isEmpty()) ? taskDO.getJmx().split(",") : new String[]{};
        for (String jmxFileId : jmxFileIds){
            jFileService.downloadFile(Integer.valueOf(jmxFileId), tmpDir);
        }
        String[] jarFileIds = (taskDO.getJar()!= null &&!taskDO.getJar().isEmpty())? taskDO.getJar().split(",") : new String[]{};
        for (String jarFileId : jarFileIds){
            jFileService.downloadFile(Integer.valueOf(jarFileId), dependencyDir);
        }
    }

    public void downloadConfigFile_local(TaskDO taskDO, JFileService jFileService, MachineCutFileVO machineCutFileVO) {
        String tmpDir = Paths.get(this.path, "tmp").toString();
        String dependencyDir = Paths.get(this.path, "tmp", "dependencies").toString();
        log.info("开始从本地存储下载配置文件，任务ID: {}", taskDO.getTaskId());
        File tmpDirFile = new File(tmpDir);
        File dependencyDirFile = new File(dependencyDir);
        if (!tmpDirFile.exists()) {
            tmpDirFile.mkdirs();
        }

        if (!dependencyDirFile.exists()) {
            dependencyDirFile.mkdirs();
        }

        long startTime = System.currentTimeMillis();
        int totalFiles = 0;
        long totalBytes = 0L;

        try {
            if (machineCutFileVO != null && machineCutFileVO.getMachineDOCutFileVOListMap() != null) {
                Map<String, List<CutFileVO>> cutFilesMap = machineCutFileVO.getMachineDOCutFileVOListMap();

                for (Map.Entry<String, List<CutFileVO>> entry : cutFilesMap.entrySet()) {
                    if (((String) entry.getKey()).equals(this.getAddress())) {
                        List<CutFileVO> cutFiles = (List) entry.getValue();
                        if (cutFiles != null && !cutFiles.isEmpty()) {
                            log.info("下载{}个切分文件到目录: {}", cutFiles.size(), tmpDir);
                            jFileService.downloadCutFile(cutFiles, tmpDir);
                            totalFiles += cutFiles.size();
                        }
                    }
                }
            }

            List<String> zipFilesToExtract = new ArrayList();
            if (taskDO.getCsv() != null && !taskDO.getCsv().isEmpty()) {
                String[] csvFileIds = taskDO.getCsv().split(",");
                log.info("开始下载{}个CSV文件", csvFileIds.length);

                for (String csvFileId : csvFileIds) {
                    JFileDO fileDO = jFileService.searchById(Integer.valueOf(csvFileId));
                    if (fileDO != null && !fileDO.getCut()) {
                        String filePath = jFileService.downloadFile(fileDO.getId(), tmpDir);
                        ++totalFiles;
                        totalBytes += fileDO.getSize();
                        if (filePath != null && filePath.toLowerCase().endsWith(".zip")) {
                            zipFilesToExtract.add(filePath);
                            log.info("添加CSV压缩包到解压列表: {}", filePath);
                        }
                    }
                }
            }

            if (taskDO.getJmx() != null && !taskDO.getJmx().isEmpty()) {
                String[] jmxFileIds = taskDO.getJmx().split(",");
                log.info("开始下载{}个JMX文件", jmxFileIds.length);

                for (String jmxFileId : jmxFileIds) {
                    JFileDO fileDO = jFileService.searchById(Integer.valueOf(jmxFileId));
                    if (fileDO != null) {
                        String filePath = jFileService.downloadFile(fileDO.getId(), tmpDir);
                        ++totalFiles;
                        totalBytes += fileDO.getSize();
                        if (filePath != null && filePath.toLowerCase().endsWith(".zip")) {
                            zipFilesToExtract.add(filePath);
                            log.info("添加JMX压缩包到解压列表: {}", filePath);
                        }
                    }
                }
            }

            if (taskDO.getJar() != null && !taskDO.getJar().isEmpty()) {
                String[] jarFileIds = taskDO.getJar().split(",");
                log.info("开始下载{}个JAR文件到依赖目录", jarFileIds.length);

                for (String jarFileId : jarFileIds) {
                    JFileDO fileDO = jFileService.searchById(Integer.valueOf(jarFileId));
                    if (fileDO != null) {
                        String filePath = jFileService.downloadFile(fileDO.getId(), dependencyDir);
                        ++totalFiles;
                        totalBytes += fileDO.getSize();
                        if (filePath != null && filePath.toLowerCase().endsWith(".zip")) {
                            zipFilesToExtract.add(filePath);
                            log.info("添加JAR压缩包到解压列表: {}", filePath);
                        }
                    }
                }
            }

            if (!zipFilesToExtract.isEmpty()) {
                log.info("开始解压{}个ZIP文件", zipFilesToExtract.size());
                long extractStartTime = System.currentTimeMillis();
                int processors = Math.min(Runtime.getRuntime().availableProcessors(), zipFilesToExtract.size());
                ExecutorService executor = Executors.newFixedThreadPool(processors);

                try {
                    List<CompletableFuture<Void>> futures = new ArrayList();

                    for (String zipFilePath : zipFilesToExtract) {
                        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                            try {
                                File zipFile = new File(zipFilePath);
                                String targetDir = zipFile.getParent();
                                log.info("解压文件: {} 到目录: {}", zipFilePath, targetDir);
                                FileInputStream fis = new FileInputStream(zipFilePath);

                                try {
                                    ZipUtil.unzipFile(fis, targetDir);
                                } catch (Throwable var7) {
                                    try {
                                        fis.close();
                                    } catch (Throwable var6) {
                                        var7.addSuppressed(var6);
                                    }

                                    throw var7;
                                }

                                fis.close();
                                log.info("解压完成: {}", zipFilePath);
                                if (zipFile.delete()) {
                                    log.info("已删除原始ZIP文件: {}", zipFilePath);
                                }
                            } catch (Exception e) {
                                log.error("解压文件失败: {}, 错误: {}", new Object[]{zipFilePath, e.getMessage(), e});
                            }

                        }, executor);
                        futures.add(future);
                    }

                    CompletableFuture.allOf((CompletableFuture[]) futures.toArray(new CompletableFuture[0])).join();
                    long extractTime = System.currentTimeMillis() - extractStartTime;
                    log.info("所有ZIP文件解压完成，耗时: {}毫秒", extractTime);
                } finally {
                    executor.shutdown();
                }
            }

            long totalTime = System.currentTimeMillis() - startTime;
            double transferSpeed = (double) totalBytes / (double) 1024.0F / (double) 1024.0F / ((double) totalTime / (double) 1000.0F);
            log.info("配置文件下载完成，共{}个文件，总大小: {}MB，总耗时: {}毫秒，传输速度: {}MB/s", new Object[]{totalFiles, totalBytes / 1048576L, totalTime, transferSpeed});
            File[] tmpFiles = tmpDirFile.listFiles();
            if (tmpFiles != null && tmpFiles.length > 0) {
                log.info("tmp目录中的文件列表:");

                for (File file : tmpFiles) {
                    log.info(" - {} ({}KB)", file.getName(), file.length() / 1024L);
                }
            }

        } catch (Exception e) {
            log.error("下载配置文件过程中发生错误: {}", e.getMessage(), e);
            throw new RuntimeException("下载配置文件失败", e);
        }
    }

    public String mergeJtlFile(TaskDO taskDO, JFileService jFileService) {
        List<JFileDO> jFileDOS = jFileService.searchJtlByTaskId(taskDO.getTaskId());
        log.info("为任务 {} 查询到 {} 个JTL文件", taskDO.getTaskId(), jFileDOS.size());
        if (jFileDOS.isEmpty()) {
            log.warn("任务 {} 没有找到JTL文件", taskDO.getTaskId());
            return null;
        }
        for (JFileDO file : jFileDOS) {
            log.info("找到JTL文件: ID={}, 名称={}, 路径={}", new Object[]{file.getId(), file.getName(), file.getPath()});
        }
        String newJtlPath = Paths.get(jFileService.getStoreDir(), new String[] { taskDO.getTaskId() + ".jtl" }).toString();
        log.info("开始合并{}个JTL文件到: {}", jFileDOS.size(), newJtlPath);
        List<String> filePaths = Collections.synchronizedList(new ArrayList<>());
        int processors = Math.min(Runtime.getRuntime().availableProcessors(), jFileDOS.size());
        ExecutorService executor = Executors.newFixedThreadPool(processors);
        try {
            List<CompletableFuture<Void>> futures = new ArrayList<>();
            for (JFileDO file : jFileDOS) {
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    String filePath = jFileService.downloadFile(file.getId(), null);
                    if (filePath != null) {
                        filePaths.add(filePath);
                        log.info("{}", filePath);
                    } else {
                        log.warn("ID={}, {}", file.getId(), file.getName());
                    }
                },executor);
                futures.add(future);
            }
            CompletableFuture.allOf((CompletableFuture[])futures.toArray(new CompletableFuture[0])).join();
            log.info("共下载了 {} 个JTL文件", filePaths.size());
            if (filePaths.isEmpty()) {
                return null;
            }
            List<String> validPaths = new ArrayList<>();
            for (String path : filePaths) {
                File file = new File(path);
                if (file.exists() && file.canRead() && file.length() > 0L) {
                    validPaths.add(path);
                    continue;
                }
                log.warn("JTL{}", path);
            }
            if (validPaths.isEmpty()) {
                log.error("没有有效的JTL文件可合并");
                return null;
            }
            BufferedWriter writer = Files.newBufferedWriter(Paths.get(newJtlPath), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
            try {
                log.info("{}", validPaths.get(0));
                String headerLine = null;
                BufferedReader reader = Files.newBufferedReader(Paths.get(validPaths.get(0), new String[0]));
                try {
                    headerLine = reader.readLine();
                    if (headerLine == null) {
                        log.error("JTL{}", validPaths.get(0));
                        String str = null;
                        if (reader != null)
                            reader.close();
                        if (writer != null)
                            writer.close();
                        return str;
                    }
                    writer.write(headerLine);
                    writer.newLine();
                    log.info("{}", headerLine);
                    if (reader != null)
                        reader.close();
                } catch (Throwable throwable) {
                    if (reader != null)
                        try {
                            reader.close();
                        } catch (Throwable throwable1) {
                            throwable.addSuppressed(throwable1);
                        }
                    throw throwable;
                }
                char[] buffer = new char[8388608];
                for (String filePath : validPaths) {
                    log.info("{}", filePath);
                    try {
                        BufferedReader bufferedReader = Files.newBufferedReader(Paths.get(filePath, new String[0]));
                        try {
                            bufferedReader.readLine();
                            int lineCount = 0;
                            String line;
                            while ((line = bufferedReader.readLine()) != null) {
                                writer.write(line);
                                writer.newLine();
                                lineCount++;
                                if (lineCount % 100000 == 0)
                                    log.info("{} {}", Integer.valueOf(lineCount), filePath);
                            }
                            log.info("文件 {} 处理完成，共 {} 行", filePath, lineCount);
                            if (bufferedReader != null)
                                bufferedReader.close();
                        } catch (Throwable throwable) {
                            if (bufferedReader != null)
                                try {
                                    bufferedReader.close();
                                } catch (Throwable throwable1) {
                                    throwable.addSuppressed(throwable1);
                                }
                            throw throwable;
                        }
                    } catch (IOException e) {
                        log.error("{}", filePath, e);
                    }
                }
                if (writer != null)
                    writer.close();
            } catch (Throwable throwable) {
                if (writer != null)
                    try {
                        writer.close();
                    } catch (Throwable throwable1) {
                        throwable.addSuppressed(throwable1);
                    }
                throw throwable;
            }
            for (String filePath : filePaths) {
                try {
                    Files.deleteIfExists(Paths.get(filePath, new String[0]));
                } catch (IOException e) {
                    log.warn("{}", filePath, e);
                }
            }
            File mergedFile = new File(newJtlPath);
            if (!mergedFile.exists() || mergedFile.length() == 0L) {
                log.error("合并后的JTL文件不存在或为空");
                return null;
            }
            log.info("{}MB", Long.valueOf(Files.size(Paths.get(newJtlPath, new String[0])) / 1048576L));
            return newJtlPath;
        } catch (Exception e) {
            log.error("合并JTL文件失败", e);
            return null;
        } finally {
            executor.shutdown();
        }
    }

    public String generateReport(TaskDO taskDO, String jtlPath, JFileService jFileService) {
        if (jtlPath == null) {
            log.error("JTL文件路径为空，无法生成报告");
            return null;
        } else {
            try {
                long fileSize = Files.size(Paths.get(jtlPath));
                if (fileSize > 1000000000L) {
                    log.warn("JTL文件较大 ({}MB)，报告生成可能需要较长时间", fileSize / 1048576L);
                    JMeterUtils.setProperty("jmeter.reportgenerator.apdex_satisfied_threshold", "2000");
                    JMeterUtils.setProperty("jmeter.reportgenerator.apdex_tolerated_threshold", "4000");
                    JMeterUtils.setProperty("jmeter.reportgenerator.memory.gc", "true");
                    JMeterUtils.setProperty("jmeter.reportgenerator.graph.granularity", "60000");
                }
            } catch (IOException e) {
                log.warn("无法检查JTL文件大小", e);
            }

            String outputReportPath = Paths.get(jFileService.getStoreDir(), "report_" + taskDO.getTaskId()).toString();
            log.info("开始生成报告，输出路径: {}", outputReportPath);

            try {
                Files.createDirectories(Paths.get(outputReportPath));
                JMeterUtils.setProperty("jmeter.reportgenerator.outputdir", outputReportPath);
                JMeterUtils.setProperty("jmeter.save.saveservice.output_format", "csv");
                JMeterUtils.setProperty("jmeter.reportgenerator.overall_granularity", "1000");
                JMeterUtils.setProperty("jmeter.reportgenerator.report_title", "性能测试报告 - " + taskDO.getTaskId());
                ReportGenerator generator = new ReportGenerator(jtlPath, (ResultCollector) null);
                generator.generate();
                log.info("报告生成成功: {}", outputReportPath);
                return outputReportPath;
            } catch (Exception e) {
                log.error("生成报告失败", e);
                return null;
            }
        }
    }

    public JFileDO compressReportAndUpload(TaskDO taskDO, String reportPath, JFileService jFileService) {
        String reportZipPath = Paths.get(jFileService.getStoreDir(), "report_" + taskDO.getTaskId() + ".zip").toString();

        try {
            ZipUtil.zipFolderWithArchiveOutputStream(reportPath, reportZipPath);
        } catch (IOException e) {
            log.error("压缩报告失败", e);
            throw new RuntimeException(e);
        }

        JFileDO file = jFileService.createFile(reportZipPath);
        file.setTaskId(taskDO.getTaskId());
        jFileService.updateById(file);
        log.info("压缩上传报告成功");
        return file;
    }

    public void serverCollect(TaskDO taskDO, JFileService jFileService, ReportRepository reportRepository) {
        log.info("开始收集和处理任务 {} 的测试数据", taskDO.getTaskId());
        long startTime = System.currentTimeMillis();
        this.initServerJmeterUtils(taskDO);

        try {
            log.info("开始合并JTL文件...");
            long mergeStart = System.currentTimeMillis();
            String jtlPath = this.mergeJtlFile(taskDO, jFileService);
            log.info("JTL文件合并完成，耗时: {}秒", (System.currentTimeMillis() - mergeStart) / 1000L);
            if (jtlPath == null) {
                log.error("任务 {} 的JTL文件合并失败，无法生成报告", taskDO.getTaskId());
                return;
            }

            boolean useDistributedProcessing = shouldUseDistributedProcessing(jtlPath);
            if (useDistributedProcessing) {
                List<JFileDO> statsFiles = jFileService.searchStatsByTaskId(taskDO.getTaskId());
                if (!statsFiles.isEmpty()) {
                    log.info("使用分布式处理模式：找到 {} 个预处理统计文件", statsFiles.size());
                    String outputReportPath = this.generateReport(taskDO, jtlPath, jFileService);
                    if (outputReportPath == null) {
                        log.error("任务 {} 的HTML报告生成失败", taskDO.getTaskId());
                        return;
                    }
                    JFileDO reportFile = this.compressReportAndUpload(taskDO, outputReportPath, jFileService);
                    log.info("开始合并预处理统计文件...");
                    FastJtlProcessor processor = new FastJtlProcessor();
                    ReportDO reportDO = processor.mergeStatsFiles(taskDO, statsFiles, jFileService, reportFile);
                    if (reportDO != null) {
                        try {
                            reportRepository.save(reportDO);
                            log.info("分布式处理报告已保存到MongoDB");
                        } catch (Exception e) {
                            log.error("保存分布式处理报告到MongoDB失败: {}", e.getMessage(), e);
                        }
                        try {
                            Files.deleteIfExists(Paths.get(jtlPath));
                            log.info("清理临时JTL文件: {}", jtlPath);
                        } catch (IOException e) {
                            log.warn("删除临时JTL文件失败: {}", jtlPath, e);
                        }
                        log.info("分布式处理报告生成完成，总耗时: {}秒", (System.currentTimeMillis() - startTime) / 1000L);
                        return;
                    }
                    log.warn("分布式处理失败，将回退到标准处理");
                } else {
                    log.warn("文件大小适合分布式处理，但未找到预处理统计文件，将使用标准处理");
                }
            } else {
                log.info("JTL文件大小适合使用标准处理方式");
            }
            log.info("使用标准处理流程生成报告");
            log.info("开始生成HTML报告...");
            long reportStart = System.currentTimeMillis();
            String outputReportPath = this.generateReport(taskDO, jtlPath, jFileService);
            if (outputReportPath == null) {
                log.error("任务 {} 的HTML报告生成失败", taskDO.getTaskId());
                return;
            }
            log.info("开始压缩报告...");
            JFileDO reportFile = this.compressReportAndUpload(taskDO, outputReportPath, jFileService);
            log.info("报告压缩上传完成，耗时: {}秒", (System.currentTimeMillis() - reportStart) / 1000L);
            log.info("开始处理JTL数据...");
            long processStart = System.currentTimeMillis();
            
            // 使用JtlDataProcessor处理基础统计数据
            JtlDataProcessor processor = new JtlDataProcessor();
            ReportDO reportDO = processor.processJtlData(taskDO, jtlPath, reportFile);
            log.info("JTL数据处理完成，耗时: {}秒", (System.currentTimeMillis() - processStart) / 1000L);
            
            if (reportDO != null) {
                try {
                    // 使用ReportDataProcess处理详细的图表数据
                    log.info("开始使用ReportDataProcess处理详细图表数据...");
                    long graphStart = System.currentTimeMillis();
                    ReportDO graphData = new ReportDataProcess().getData(taskDO, outputReportPath, reportFile);
                    log.info("图表数据处理完成，耗时: {}秒", (System.currentTimeMillis() - graphStart) / 1000L);
                    
                    // 合并统计数据和图表数据
                    if (graphData != null && graphData.getGraphData() != null) {
                        reportDO.setGraphData(graphData.getGraphData());
                        log.info("成功合并统计数据和图表数据");
                    } else {
                        log.warn("图表数据处理失败或为空，将使用空的图表数据");
                        reportDO.setGraphData(new HashMap<>());
                    }
                    // 保存到MongoDB
                    reportRepository.save(reportDO);
                    log.info("报告数据保存到MongoDB成功");
                } catch (Exception e) {
                    log.error("保存报告数据到MongoDB失败: {}", e.getMessage(), e);
                }
            } else {
                log.error("任务 {} 的报告数据处理失败", taskDO.getTaskId());
            }
            try {
                Files.deleteIfExists(Paths.get(jtlPath));
                log.info("清理临时JTL文件: {}", jtlPath);
            } catch (IOException e) {
                log.warn("删除临时JTL文件失败: {}", jtlPath, e);
            }

            log.info("任务 {} 的报告生成全部完成，总耗时: {}秒", taskDO.getTaskId(), (System.currentTimeMillis() - startTime) / 1000L);
        } catch (Exception e) {
            log.error("任务 {} 的报告处理过程发生错误: {}", new Object[]{taskDO.getTaskId(), e.getMessage(), e});
        }
    }

    public String getAddress() {
        return this.address;
    }
    
    public String getVersion() {
        return this.version;
    }
    
    public String getPath() {
        return this.path;
    }
    
    public Boolean getIsOnline() {
        return this.isOnline;
    }
    
    public Socket getSocket() {
        return this.socket;
    }

}